package com.dashan.backend.geosearch;

import static com.dashan.backend.geosearch.GeoMath.MAX_LEVEL;
import static com.dashan.backend.geosearch.GeoMath.extractLong;
import static com.dashan.backend.geosearch.GeoMath.indexX2MapX;
import static com.dashan.backend.geosearch.GeoMath.indexY2MapY;
import static com.dashan.backend.geosearch.GeoMath.interleaveLong;
import static com.dashan.backend.geosearch.GeoMath.lat2MapY;
import static com.dashan.backend.geosearch.GeoMath.lon2MapX;
import static com.dashan.backend.geosearch.GeoMath.mapX2IndexX;
import static com.dashan.backend.geosearch.GeoMath.mapX2Lon;
import static com.dashan.backend.geosearch.GeoMath.mapY2IndexY;
import static com.dashan.backend.geosearch.GeoMath.mapY2Lat;

import com.dashan.backend.DashanException;

public class GeoCell {
    private static final String ZEROS = 
        "0000000000000000000000000000000000000000000000000000000000000000";
    private int level;
    private int indexX;
    private int indexY;
    private long mortonNumber;
    
    public GeoCell(int level, long mortonNumber) {
        this.level = level;
        this.mortonNumber = mortonNumber;
        long[] xy = extractLong(mortonNumber);
        this.indexX = (int) xy[0];
        this.indexY = (int) xy[1];
    }
    
    public GeoCell(int level, int x, int y) {
        this.level = level;
        this.indexX = x;
        this.indexY = y;
        this.mortonNumber = interleaveLong((long) x, (long) y);
    }
    
    public GeoCell(double lonInDegree, double latInDegree) {
        this(MAX_LEVEL, (int) mapX2IndexX(MAX_LEVEL, lon2MapX(lonInDegree)),
                        (int) mapY2IndexY(MAX_LEVEL, lat2MapY(latInDegree)));
    }
    
    public GeoCell(int level, double mapX, double mapY) {
        this(level, (int) mapX2IndexX(level, mapX),
                    (int) mapY2IndexY(level, mapY)); 
    }
    
    public long getEncodedLong() {
        return (level << GeoMath.CELL_BITWIDTH) | mortonNumber;
    }
 
    public GeoCell castToBiggerCell(int level) throws DashanException {
        if (level > this.level) {
            throw new DashanException("Can only cast to larger cell");
        }
        
        return new GeoCell(level, mortonNumber >> ((this.level - level) * 2));
    }
    
    public int getLevel() { return level;}

    public int getIndexX() { return indexX;}

    public int getIndexY() { return indexY;}
    
    /** @return longitude in Degree */
    public double getLeftLon() {
        return mapX2Lon(indexX2MapX(level, indexX));
    }
    
    /** @return longitude in Degree */
    public double getRightLon() {
        return mapX2Lon(indexX2MapX(level, indexX + 1));
    }
    
    /** @return latitude in Degree */
    public double getTopLat() {
        return mapY2Lat(indexY2MapY(level, indexY));
    }

    /** @return latitude in Degree */
    public double getBottomLat() {
        return mapY2Lat(indexY2MapY(level, indexY + 1));
    }
    
    public double getLatSpan() {
        return getTopLat() - getBottomLat();
    }
    
    public double getLonSpan() {
        return getRightLon() - getLeftLon();
    }
    
    public long getMortonNumber() { return mortonNumber;}
    
    @Override
    public String toString() {
        String s = Long.toBinaryString(mortonNumber);
        return ZEROS.substring(0, Math.max(0, (level * 2) - s.length())) + s;
    }
}
